using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace Magiq.Support
{
    public abstract class ExpressionVisitor
    {
        public TExpression Visit<TExpression>(TExpression expression) where TExpression : Expression
        {
            return (TExpression) Dispatch(expression);
        }

        public IEnumerable<TExpression> VisitMany<TExpression>(IEnumerable<TExpression> expressions)
            where TExpression : Expression
        {
            return expressions.Select(Visit);
        }

        protected virtual Expression Dispatch(Expression expression)
        {
            if (expression == null)
                return null;
            switch (expression.NodeType)
            {
                case ExpressionType.Add:
                case ExpressionType.AddChecked:
                case ExpressionType.And:
                case ExpressionType.AndAlso:
                case ExpressionType.Coalesce:
                case ExpressionType.Divide:
                case ExpressionType.Equal:
                case ExpressionType.ExclusiveOr:
                case ExpressionType.GreaterThan:
                case ExpressionType.GreaterThanOrEqual:
                case ExpressionType.LeftShift:
                case ExpressionType.LessThan:
                case ExpressionType.LessThanOrEqual:
                case ExpressionType.Modulo:
                case ExpressionType.Multiply:
                case ExpressionType.MultiplyChecked:
                case ExpressionType.NotEqual:
                case ExpressionType.Or:
                case ExpressionType.OrElse:
                case ExpressionType.Power:
                case ExpressionType.RightShift:
                case ExpressionType.Subtract:
                case ExpressionType.SubtractChecked:
                case ExpressionType.ArrayIndex:
                    return VisitBinary((BinaryExpression) expression);

                case ExpressionType.ArrayLength:
                case ExpressionType.Convert:
                case ExpressionType.ConvertChecked:
                case ExpressionType.Negate:
                case ExpressionType.UnaryPlus:
                case ExpressionType.NegateChecked:
                case ExpressionType.Not:
                case ExpressionType.Quote:
                case ExpressionType.TypeAs:
                    return VisitUnary((UnaryExpression) expression);

                case ExpressionType.Call:
                    return VisitCall((MethodCallExpression) expression);

                case ExpressionType.Conditional:
                    return VisitConditional((ConditionalExpression) expression);

                case ExpressionType.Constant:
                    return VisitConstant((ConstantExpression) expression);

                case ExpressionType.Invoke:
                    return VisitInvoke((InvocationExpression) expression);

                case ExpressionType.ListInit:
                    return VisitListInit((ListInitExpression) expression);

                case ExpressionType.MemberAccess:
                    return VisitMember((MemberExpression) expression);

                case ExpressionType.MemberInit:
                    return VisitMemberInit((MemberInitExpression) expression);

                case ExpressionType.New:
                    return VisitNew((NewExpression) expression);

                case ExpressionType.NewArrayInit:
                    return VisitNewArrayInit((NewArrayExpression) expression);

                case ExpressionType.NewArrayBounds:
                    return VisitNewArrayBounds((NewArrayExpression) expression);

                case ExpressionType.Parameter:
                    return VisitParameter((ParameterExpression) expression);

                case ExpressionType.TypeIs:
                    return VisitTypeIs((TypeBinaryExpression) expression);

                case ExpressionType.Lambda:
                    return VisitLambda((LambdaExpression) expression);

                default:
                    throw new ArgumentOutOfRangeException();
            }
        }

        protected abstract Expression VisitConstant(ConstantExpression expression);
        protected abstract Expression VisitParameter(ParameterExpression expression);

        protected abstract Expression VisitLambda(LambdaExpression expression);
        protected abstract Expression VisitNewArrayBounds(NewArrayExpression expression);
        protected abstract Expression VisitNewArrayInit(NewArrayExpression expression);
        protected abstract MemberBinding Binding(MemberBinding binding);
        protected abstract Expression VisitMemberInit(MemberInitExpression expression);
        protected abstract Expression VisitListInit(ListInitExpression expression);
        protected abstract ElementInit VisitElementInit(ElementInit elementInit);
        protected abstract Expression VisitNew(NewExpression expression);
        protected abstract Expression VisitInvoke(InvocationExpression expression);
        protected abstract Expression VisitConditional(ConditionalExpression expression);
        protected abstract Expression VisitBinary(BinaryExpression expression);
        protected abstract Expression VisitUnary(UnaryExpression expression);
        protected abstract Expression VisitMember(MemberExpression expression);
        protected abstract Expression VisitTypeIs(TypeBinaryExpression expression);
        protected abstract Expression VisitCall(MethodCallExpression expression);
    }
}